home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
pcl
/
cl-nd-cl.lha
/
clue
/
clio
/
scroller.lisp
< prev
next >
Wrap
Text File
|
1991-07-15
|
52KB
|
1,346 lines
;; -*- Mode:Lisp; Package:CLIO-OPEN; Base:10; Lowercase:T; Fonts:(CPTFONT); Syntax:Common-Lisp -*-
;;;----------------------------------------------------------------------------------+
;;; |
;;; TEXAS INSTRUMENTS INCORPORATED |
;;; P.O. BOX 149149 |
;;; AUSTIN, TEXAS 78714 |
;;; |
;;; Copyright (C) 1989, 1990 Texas Instruments Incorporated. |
;;; |
;;; Permission is granted to any individual or institution to use, copy, modify, and |
;;; distribute this software, provided that this complete copyright and permission |
;;; notice is maintained, intact, in all copies and supporting documentation. |
;;; |
;;; Texas Instruments Incorporated provides this software "as is" without express or |
;;; implied warranty. |
;;; |
;;;----------------------------------------------------------------------------------+
(in-package "CLIO-OPEN")
(export '(
make-scroller
scale-increment
scale-indicator-size
scale-maximum
scale-minimum
scale-orientation
scale-update
scale-update-delay
scale-value
scroller
)
'clio-open)
;;;----------------------------------------------------------------------------+
;;; |
;;; Scroller |
;;; |
;;;----------------------------------------------------------------------------+
;; Implementation Strategy:
;;
;; Elevator and anchor controls are implemented as non-contact subwindows (i.e.
;; scroller is NOT a composite). Overall, this strategy simplifies the tasks
;; of determining which control is receiving input and of confining the pointer
;; cursor to controls during continuous scrolling, without unnecessarily
;; incurring the full cost of a sub-contact.
;; The elevator is represented as an :input-output subwindow; the imagery of all
;; elevator controls is drawn to this subwindow. The less/more arrow and drag
;; area controls are represented as :input-only subwindows of the elevator
;; subwindow. Subwindows are used for these controls only for use as :confine-to
;; windows while the pointer is grabbed.
;; All control subwindows are recorded in the regions vector of the scroller.
;; The scroller subwindow receiving a :button-press is determined from the child
;; slot of the button event. During event handling, the regions vector is
;; searched to look up the vector index of the event window (see FIND-REGION).
;; If the event child is the elevator, then a further search based on elevator
;; geometry is necessary to determine which elevator subwindow is the event
;; window. The resulting vector index is used to select an element from a
;; vector of functions to handle the :button-press (see PRESS-HANDLERS).
(defcontact scroller (core contact)
((increment :type number
:reader scale-increment ; setf defined below
:initarg :increment
:initform 1)
(indicator-size
:type (or number (member :off))
:reader scale-indicator-size ; setf defined below
:initarg :indicator-size
:initform 0)
(maximum :type number
:reader scale-maximum ; setf defined below
:initarg :maximum
:initform 1)
(minimum :type number
:reader scale-minimum ; setf defined below
:initarg :minimum
:initform 0)
(orientation :type (member :horizontal :vertical)
:reader scale-orientation ; setf defined below
:initarg :orientation
:initform :vertical)
(update-delay :type (or number (member :until-done))
:reader scale-update-delay ; setf defined below
:initarg :update-delay
:initform 0)
(value :type number
:reader scale-value ; setf defined below
:initarg :value
:initform 0)
(compress-exposures
:initform :on
:type (member :off :on)
:reader contact-compress-exposures
:allocation :class)
(regions :type (vector window)
:initform (make-array 6)))
(:resources
increment indicator-size maximum minimum orientation update-delay value
(border-width :initform 0)
(event-mask :initform #.(make-event-mask :pointer-motion-hint :exposure))))
;; Index values for accessing region vector and press handler vector
(defconstant *elevator-region* 0)
(defconstant *min-anchor-region* 1)
(defconstant *max-anchor-region* 2)
(defconstant *less-arrow-region* 3)
(defconstant *drag-area-region* 4)
(defconstant *more-arrow-region* 5)
(defconstant *cable-region* 6)
;;;----------------------------------------------------------------------------+
;;; |
;;; Initialization |
;;; |
;;;----------------------------------------------------------------------------+
(defun make-scroller (&rest initargs &key &allow-other-keys)
(apply #'make-contact 'scroller initargs))
(defmethod initialize-instance :after ((self scroller) &key &allow-other-keys)
(with-slots (width height) self
;; Initialize required geometry
(multiple-value-setq (width height) (preferred-size self))))
(defun min-anchor-geometry (dimensions scroller orientation width height)
(declare (ignore scroller))
(let ((anchor-width (scrollbar-anchor-width dimensions))
(anchor-height (scrollbar-anchor-height dimensions)))
(if (eq orientation :vertical)
(values (pixel-round (- width anchor-width) 2) 0 (- anchor-width 2) (- anchor-height 2))
(values 0 (pixel-round (- height anchor-width) 2) (- anchor-height 2) (- anchor-width 2)))))
(defun max-anchor-geometry (dimensions scroller orientation width height)
(declare (ignore scroller))
(let ((anchor-width (scrollbar-anchor-width dimensions))
(anchor-height (scrollbar-anchor-height dimensions)))
(if (eq orientation :vertical)
(values (pixel-round (- width anchor-width) 2) (- height anchor-height) (- anchor-width 2) (- anchor-height 2))
(values (- width anchor-height) (pixel-round (- height anchor-width) 2) (- anchor-height 2) (- anchor-width 2)))))
(defun elevator-geometry (dimensions scroller orientation width height)
(let ((anchor-width (scrollbar-anchor-width dimensions)))
(if (eq orientation :vertical)
(values (pixel-round (- width anchor-width) 2) (scroller-value-position scroller) anchor-width (scrollbar-elevator-size scroller))
(values (scroller-value-position scroller) (pixel-round (- height anchor-width) 2) (scrollbar-elevator-size scroller) anchor-width))))
(defun less-arrow-geometry (dimensions scroller orientation width height)
(declare (ignore scroller orientation width height))
(let ((anchor-width (scrollbar-anchor-width dimensions)))
(values 0 0 anchor-width anchor-width)))
(defun more-arrow-geometry (dimensions scroller orientation width height)
(declare (ignore width height))
(let ((anchor-width (scrollbar-anchor-width dimensions))
(abbreviated-p (scrollbar-abbreviated-p scroller)))
(if (eq orientation :vertical)
(values 0 (+ anchor-width (if abbreviated-p 0 anchor-width)) anchor-width anchor-width)
(values (+ anchor-width (if abbreviated-p 0 anchor-width)) 0 anchor-width anchor-width))))
(defun drag-area-geometry (dimensions scroller orientation width height)
(declare (ignore scroller width height))
(let ((anchor-width (scrollbar-anchor-width dimensions)))
(if (eq orientation :vertical)
(values 0 anchor-width anchor-width anchor-width)
(values anchor-width 0 anchor-width anchor-width))))
(defmethod reconfigure-controls ((self scroller))
(declare (type scroller self))
(with-slots (regions width height orientation)
self ;(the scroller self)
(let ((dimensions (getf *scrollbar-dimensions* (contact-scale self))))
(let ((window (svref regions *min-anchor-region*)))
(with-state (window)
(multiple-value-bind (window-x window-y window-width window-height)
(min-anchor-geometry dimensions self orientation width height)
(setf (drawable-x window) window-x
(drawable-y window) window-y
(drawable-width window) window-width
(drawable-height window) window-height))))
(let ((window (svref regions *max-anchor-region*)))
(with-state (window)
(multiple-value-bind (window-x window-y window-width window-height)
(max-anchor-geometry dimensions self orientation width height)
(setf (drawable-x window) window-x
(drawable-y window) window-y
(drawable-width window) window-width
(drawable-height window) window-height))))
(let ((window (svref regions *elevator-region*)))
(with-state (window)
(multiple-value-bind (window-x window-y window-width window-height)
(elevator-geometry dimensions self orientation width height)
(setf (drawable-x window) window-x
(drawable-y window) window-y
(drawable-width window) window-width
(drawable-height window) window-height))))
(let ((window (svref regions *drag-area-region*)))
(with-state (window)
(multiple-value-bind (window-x window-y window-width window-height)
(drag-area-geometry dimensions self orientation width height)
(setf (drawable-x window) window-x
(drawable-y window) window-y
(drawable-width window) window-width
(drawable-height window) window-height))))
(let ((window (svref regions *less-arrow-region*)))
(with-state (window)
(multiple-value-bind (window-x window-y window-width window-height)
(less-arrow-geometry dimensions self orientation width height)
(setf (drawable-x window) window-x
(drawable-y window) window-y
(drawable-width window) window-width
(drawable-height window) window-height))))
(let ((window (svref regions *more-arrow-region*)))
(with-state (window)
(multiple-value-bind (window-x window-y window-width window-height)
(more-arrow-geometry dimensions self orientation width height)
(setf (drawable-x window) window-x
(drawable-y window) window-y
(drawable-width window) window-width
(drawable-height window) window-height)))))))
(defmethod realize :after ((self scroller))
;; Create control region windows
(with-slots (regions width height orientation foreground)
self
(let* ((dimensions (getf *scrollbar-dimensions* (contact-scale self)))
(min-anchor (multiple-value-bind (region-x region-y region-width region-height)
(min-anchor-geometry dimensions self orientation width height)
(create-window
:parent self
:x region-x
:y region-y
:width region-width
:height region-height
:background :parent-relative
:border-width 1
:border foreground
:gravity (if (eq orientation :vertical) :north :west))))
(max-anchor (multiple-value-bind (region-x region-y region-width region-height)
(max-anchor-geometry dimensions self orientation width height)
(create-window
:parent self
:x region-x
:y region-y
:width region-width
:height region-height
:background :parent-relative
:border-width 1
:border foreground
:gravity (if (eq orientation :vertical) :north :west))))
(elevator (multiple-value-bind (region-x region-y region-width region-height)
(elevator-geometry dimensions self orientation width height)
(create-window
:parent self
:x region-x
:y region-y
:width region-width
:height region-height
:border-width 0
:gravity (if (eq orientation :vertical) :north :west))))
(drag-area (multiple-value-bind (region-x region-y region-width region-height)
(drag-area-geometry dimensions self orientation width height)
(create-window
:parent elevator
:class :input-only
:x region-x
:y region-y
:width region-width
:height region-height
:border-width 0)))
(less-arrow (multiple-value-bind (region-x region-y region-width region-height)
(less-arrow-geometry dimensions self orientation width height)
(create-window
:parent elevator
:class :input-only
:x region-x
:y region-y
:width region-width
:height region-height
:border-width 0)))
(more-arrow (multiple-value-bind (region-x region-y region-width region-height)
(more-arrow-geometry dimensions self orientation width height)
(create-window
:parent elevator
:class :input-only
:x region-x
:y region-y
:width region-width
:height region-height
:border-width 0))))
(setf (svref regions *min-anchor-region*) min-anchor)
(setf (svref regions *max-anchor-region*) max-anchor)
(setf (svref regions *elevator-region*) elevator)
(setf (svref regions *drag-area-region*) drag-area)
(setf (svref regions *less-arrow-region*) less-arrow)
(setf (svref regions *more-arrow-region*) more-arrow)
(map-subwindows self)
(map-subwindows elevator))))
(defmethod rescale ((self scroller))
(with-slots (orientation)
self
;; Request change to preferred width/height, depending on orientation.
(multiple-value-bind (rw rh) (if (eq :vertical orientation) (values 0 nil) (values nil 0))
(multiple-value-bind (pw ph) (preferred-size self :width rw :height rh)
(change-geometry self :width pw :height ph :accept-p t))))
(when (realized-p self)
(reconfigure-controls self)))
(defmethod (setf scale-orientation) (new-orientation (scroller scroller))
(with-slots (orientation width height)
scroller
(unless (eq orientation new-orientation)
(check-type new-orientation (member :horizontal :vertical))
(setf orientation new-orientation)
(multiple-value-bind (new-width new-height)
(preferred-size scroller :width height :height width)
(change-geometry scroller :width new-width :height new-height))
(reconfigure-controls scroller)))
new-orientation)
;;;----------------------------------------------------------------------------+
;;; |
;;; Accessors |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod (setf scale-update-delay) (new-update-delay (scroller scroller))
(with-slots (update-delay) scroller
(assert (or (eq new-update-delay :until-done)
(and (numberp new-update-delay) (not (minusp new-update-delay)))) ()
"~a is neither :UNTIL-DONE or a non-negative number." new-update-delay)
(setf update-delay new-update-delay)))
(defmethod (setf scale-value) (new-value (scroller scroller))
(scale-update scroller :value new-value)
new-value)
(defmethod (setf scale-minimum) (new-minimum (scroller scroller))
(scale-update scroller :minimum new-minimum)
new-minimum)
(defmethod (setf scale-maximum) (new-maximum (scroller scroller))
(scale-update scroller :maximum new-maximum)
new-maximum)
(defmethod (setf scale-increment) (new-increment (scroller scroller))
(scale-update scroller :increment new-increment)
new-increment)
(defmethod (setf scale-indicator-size) (new-indicator-size (scroller scroller))
(scale-update scroller :indicator-size new-indicator-size)
new-indicator-size)
(defmacro true-indicator-size (size)
`(if (eq ,size :off) 0 ,size))
(defmethod scale-update ((scroller scroller) &key value minimum maximum indicator-size increment)
(with-slots
((current-val value)
(current-min minimum)
(current-max maximum)
(current-ind indicator-size)
(current-inc increment)
regions
orientation)
scroller
(setf minimum (or minimum current-min)
maximum (or maximum current-max)
value (or value current-val)
indicator-size (or indicator-size current-ind)
increment (or increment current-inc))
(assert (numberp value) () "~s for :value is not a number" value)
(assert (numberp minimum) () "~s for :minimum is not a number" minimum)
(assert (numberp maximum) () "~s for :maximum is not a number" maximum)
(assert (and (numberp increment) (not (minusp increment))) ()
"~s for :increment is not a number" increment)
(assert (or (and (numberp indicator-size) (not (minusp indicator-size)))
(eq indicator-size :off))
() "~s for :indicator-size is not :off or a non-negative-number)" indicator-size)
(assert (<= minimum maximum) ()
"Minimum (~a) is greater than maximum (~a)."
minimum maximum)
(assert (<= minimum value maximum) ()
"Value (~a) must be in the range [~a, ~a]."
value minimum maximum)
(let*
((insensitive-p (not (sensitive-p scroller)))
(less-arrow-dim-p (or insensitive-p (= current-val current-min)))
(more-arrow-dim-p (or insensitive-p (= current-val current-max)))
(prev-min current-min)
(prev-max current-max)
(prev-ind current-ind)
(prev-val current-val))
(setf current-min minimum
current-max maximum
current-val value
current-ind indicator-size
current-inc increment)
;; Update display
(when (realized-p scroller)
(cond
((not (and (eql current-min prev-min) (eql current-max prev-max) (eql current-ind prev-ind)))
(display scroller))
((not (eql current-val prev-val))
;; Position elevator
(let ((position (scroller-value-position scroller))
(elevator (svref regions *elevator-region*)))
(if (eq :vertical orientation)
(setf (drawable-y elevator) position)
(setf (drawable-x elevator) position)))
;; Dim arrows, if necessary
(scrollbar-update-less-arrow scroller less-arrow-dim-p insensitive-p)
(scrollbar-update-more-arrow scroller more-arrow-dim-p insensitive-p)))))))
(defmethod (setf contact-foreground) :after (new-fg (self scroller))
(declare (ignore new-fg))
(with-slots (foreground regions) self
(setf (window-border (svref regions *min-anchor-region*)) foreground)
(setf (window-border (svref regions *max-anchor-region*)) foreground)))
;;;----------------------------------------------------------------------------+
;;; |
;;; Geometry Management |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod preferred-size ((self scroller) &key width height border-width)
(declare (ignore border-width))
(with-slots (orientation (current-height height) (current-width width)) self
(let*
((dimensions (getf *scrollbar-dimensions* (contact-scale self)))
(margin (scrollbar-margin dimensions))
(anchor-width (scrollbar-anchor-width dimensions))
(anchor-height (scrollbar-anchor-height dimensions))
;; Calculate geometry assuming :vertical orientation
(preferred-width (+ margin anchor-width margin))
(preferred-height (max
;; Suggested or current height
(or (if (eq orientation :vertical) height width)
(if (eq orientation :vertical) current-height current-width))
;; Size of abbreviated scrollbar (no drag area)
(+ anchor-height margin
anchor-width anchor-width
margin anchor-height))))
;; Return preferred geometry according to actual orientation
(values
(if (eq orientation :vertical) preferred-width preferred-height)
(if (eq orientation :vertical) preferred-height preferred-width)
0))))
(defmethod resize :around ((self scroller) new-width new-height new-border-width)
(if (realized-p self)
;; Reconfigure subwindows
(let* ((abbreviated-before-p (scrollbar-abbreviated-p self))
(resized-p (call-next-method)))
(with-slots (width height orientation regions) self
(let*
((scale (contact-scale self))
(max-anchor (svref regions *max-anchor-region*))
(dimensions (getf *scrollbar-dimensions* scale))
(anchor-position (- (if (eq orientation :vertical) height width)
(scrollbar-anchor-height dimensions)
1))
(elevator (svref regions *elevator-region*))
(elevator-position (scroller-value-position self)))
;; Reposition max anchor
(if (eq orientation :vertical)
(setf (drawable-y max-anchor) anchor-position)
(setf (drawable-x max-anchor) anchor-position))
;; Reconfigure elevator
(multiple-value-bind (elevator-size abbreviated-after-p)
(scrollbar-elevator-size self)
(with-state (elevator)
(case orientation
(:vertical
(setf (drawable-y elevator) elevator-position)
(setf (drawable-height elevator) elevator-size))
(:horizontal
(setf (drawable-x elevator) elevator-position)
(setf (drawable-width elevator) elevator-size))))
;; Changing abbreviation?
(unless (eq abbreviated-before-p abbreviated-after-p)
;; Reposition more-arrow region
(let* ((anchor-width (scrollbar-anchor-width dimensions))
(more-arrow-pos (+ anchor-width (if abbreviated-after-p 0 anchor-width))))
(if (eq orientation :vertical)
(setf (drawable-y (svref regions *more-arrow-region*)) more-arrow-pos)
(setf (drawable-x (svref regions *more-arrow-region*)) more-arrow-pos)))
;; Redisplay elevator image
(scrollbar-display-elevator self scale)))))
resized-p)
;; If not yet realized, just do it
(call-next-method)))
(defun scrollbar-abbreviated-p (scroller)
(with-slots (width height orientation) (the scroller scroller)
(let*
((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
(margin (scrollbar-margin dimensions))
(anchor-width (scrollbar-anchor-width dimensions))
(anchor-height (scrollbar-anchor-height dimensions)))
(<= (if (eq orientation :vertical) height width)
(+ anchor-height margin
anchor-width anchor-width anchor-width
margin anchor-height)))))
(defun scrollbar-elevator-size (scroller)
(let ((abbreviated-p (scrollbar-abbreviated-p scroller)))
(values
(+ (* (scrollbar-anchor-width
(getf *scrollbar-dimensions* (contact-scale scroller)))
(if abbreviated-p 2 3))
2)
abbreviated-p)))
(defun scrollbar-less-arrow-geometry (scroller)
(let ((arrow-size (1- (scrollbar-anchor-width (getf *scrollbar-dimensions*
(contact-scale scroller))))))
(if (eq :vertical (scale-orientation scroller))
(values 1 1 (- arrow-size 2) arrow-size)
(values 1 1 arrow-size (- arrow-size 2)))))
(defun scrollbar-drag-area-geometry (scroller)
(let*
((area-size (scrollbar-anchor-width
(getf *scrollbar-dimensions* (contact-scale scroller))))
(area-position (1+ area-size)))
(if (eq :vertical (scale-orientation scroller))
(values 1 area-position (- area-size 3) (1- area-size))
(values area-position 1 (1- area-size) (- area-size 3)))))
(defun scrollbar-more-arrow-geometry (scroller)
(let*
((arrow-size (scrollbar-anchor-width
(getf *scrollbar-dimensions* (contact-scale scroller))))
(arrow-position (1+ (+ arrow-size (if (scrollbar-abbreviated-p scroller) 0 arrow-size)))))
(if (eq :vertical (scale-orientation scroller))
(values 1 arrow-position (- arrow-size 3) (1- arrow-size))
(values arrow-position 1 (1- arrow-size) (- arrow-size 3)))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Display |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod display ((self scroller) &optional at-x at-y at-width at-height &key)
(with-slots (width height foreground regions orientation) self
;; Default exposed rectangle, if necessary
(setf at-x (or at-x 0)
at-y (or at-y 0)
at-width (or at-width (- width at-x))
at-height (or at-height (- height at-y)))
(let*
((scale (contact-scale self))
(dimensions (getf *scrollbar-dimensions* scale))
(anchor-width (scrollbar-anchor-width dimensions))
(anchor-height (scrollbar-anchor-height dimensions))
(margin (scrollbar-margin dimensions))
(cable-margin (scrollbar-cable-margin dimensions))
(cable-width (scrollbar-cable-width dimensions))
(elevator-position (scroller-value-position self))
(elevator-size (scrollbar-elevator-size self))
(elevator-end (+ elevator-position elevator-size)))
;;-----------------------------------------------------------------------------+
;; |
;; Draw cable. |
;; |
;; Stipple fill is relatively slow, so redrawing entire cable can cause |
;; annoying flicker. But computing the minimal cable area to redraw is |
;; complicated, because the display method is expected to update the image |
;; when the elevator moves (thus exposing a small region of the scroller |
;; previously obscured by the elevator). In this case, we must redraw the |
;; cable not only in the area exposed, but also elsewhere to cover up the |
;; previous gaps between elevator/proportion indicator/cable. |
;; |
;; The following algorithm is a compromise. If the exposed area is entirely |
;; on one side of the elevator (as it is in the case of an elevator move), |
;; then we redraw the cable only on that side. |
;; |
;;-----------------------------------------------------------------------------+
(flet
((exposed-cable-segment
(exposed-position exposed-size scroller-size)
(let ((min (+ anchor-height margin))
(max (- scroller-size margin anchor-height)))
(cond
;; Exposed area before elevator?
((>= elevator-position (+ exposed-position exposed-size))
;; Redraw only first part of cable.
(values min (- elevator-position min)))
;; Exposed area behind elevator?
((>= exposed-position elevator-end)
;; Redraw only last part of cable.
(values elevator-end (- max elevator-end)))
(t
;; Redraw all of cable.
(values min (- max min)))))))
(multiple-value-bind (cable-x cable-y cable-width cable-height)
(if (eq orientation :vertical)
(multiple-value-bind (cy ch) (exposed-cable-segment at-y at-height height)
(values (pixel-round (- width cable-width) 2) cy cable-width ch))
(multiple-value-bind (cx cw) (exposed-cable-segment at-x at-width width)
(values cx (pixel-round (- height cable-width) 2) cw cable-width)))
;; Draw exposed cable area
(using-gcontext (gc :drawable self
:fill-style :stippled
:foreground foreground
:stipple (contact-image-mask self 50%gray :depth 1))
(clear-area self :x cable-x :y cable-y :width cable-width :height cable-height)
(draw-rectangle self gc cable-x cable-y cable-width cable-height :fill-p))
;; Draw proportion indicator
(let* ((pi-size (scroller-indicator-size self))
(pi-pos (scroller-indicator-position self pi-size)))
(multiple-value-bind (pi-x pi-y pi-width pi-height margin-x margin-y margin-width margin-height)
(if (eq orientation :vertical)
(values
cable-x
pi-pos
cable-width
pi-size
cable-x
(- pi-pos margin)
cable-width
(+ pi-size margin margin))
(values
pi-pos
cable-y
pi-size
cable-height
(- pi-pos margin)
cable-y
(+ pi-size margin margin)
cable-height))
(clear-area self :x margin-x :y margin-y :width margin-width :height margin-height)
(using-gcontext (gc :drawable self
:fill-style :solid
:foreground foreground)
(draw-rectangle self gc pi-x pi-y pi-width pi-height :fill-p))))))
;; Clear cable margin around elevator
(multiple-value-bind (gap-x gap-y gap-width gap-height)
(if (eq orientation :vertical)
(values
0 (- elevator-position cable-margin)
nil (+ cable-margin elevator-size cable-margin))
(values
(- elevator-position cable-margin) 0
(+ cable-margin elevator-size cable-margin) nil))
(clear-area self :x gap-x :y gap-y :width gap-width :height gap-height))
;; Compute elevator geometry
(multiple-value-bind (elevator-x elevator-y elevator-width elevator-height)
(if (eq orientation :vertical)
(values
(scrollbar-margin dimensions)
elevator-position
anchor-width
elevator-size)
(values
elevator-position
(scrollbar-margin dimensions)
elevator-size
anchor-width))
(when
;; Exposed area intersects elevator?
(and (< elevator-x (+ at-x at-width))
(< elevator-y (+ at-y at-height))
(> (+ elevator-x elevator-width) at-x)
(> (+ elevator-y elevator-height) at-y))
(scrollbar-display-elevator self scale))))))
(defun scrollbar-display-elevator (scroller &optional scale)
(setf scale (or scale (contact-scale scroller)))
(with-slots (orientation regions foreground) (the scroller scroller)
;; Draw elevator image
(let*
((image (getf (getf *scrollbar-images* orientation) scale))
(mask (contact-image-mask
scroller image
:foreground foreground
:background (contact-current-background-pixel scroller)))
(elevator (svref regions *elevator-region*)))
(using-gcontext (gc :drawable scroller :exposures :off)
(copy-area
mask gc
0 0
(image-width image) (image-height image)
elevator
0 0)
(when (scrollbar-abbreviated-p scroller)
(let ((copy-size (scrollbar-anchor-width (getf *scrollbar-dimensions* scale))))
(multiple-value-bind (from-x from-y copy-width copy-height)
(if (eq :vertical orientation)
(values 0 (+ copy-size copy-size) copy-size (+ copy-size 2))
(values (+ copy-size copy-size) 0 (+ copy-size 2) copy-size))
(multiple-value-bind (to-x to-y)
(if (eq :vertical orientation)
(values 0 copy-size)
(values copy-size 0))
(copy-area
mask gc
from-x from-y
copy-width copy-height
elevator
to-x to-y))))))))
;; Dim arrows, if necessary
(let ((insensitive-p (not (sensitive-p scroller))))
(scrollbar-update-less-arrow scroller nil insensitive-p)
(scrollbar-update-more-arrow scroller nil insensitive-p)))
(defun scrollbar-update-less-arrow (scroller dim-p insensitive-p)
(with-slots (value minimum foreground regions) (the scroller scroller)
(unless (eq dim-p (or insensitive-p (= value minimum)))
(multiple-value-bind (arrow-x arrow-y arrow-width arrow-height)
(scrollbar-less-arrow-geometry scroller)
(using-gcontext
(gc :drawable scroller
:function boole-xor
:fill-style :stippled
:foreground (logxor foreground (contact-current-background-pixel scroller))
:stipple (contact-image-mask scroller 25%gray :depth 1))
(draw-rectangle
(svref regions *elevator-region*) gc
arrow-x arrow-y
arrow-width arrow-height
:fill-p))))))
(defun scrollbar-update-more-arrow (scroller dim-p insensitive-p)
(with-slots (value maximum foreground regions) (the scroller scroller)
(unless (eq dim-p (or insensitive-p (= value maximum)))
(multiple-value-bind (arrow-x arrow-y arrow-width arrow-height)
(scrollbar-more-arrow-geometry scroller)
(using-gcontext
(gc :drawable scroller
:function boole-xor
:fill-style :stippled
:foreground (logxor foreground (contact-current-background-pixel scroller))
:stipple (contact-image-mask scroller 25%gray :depth 1))
(draw-rectangle
(svref regions *elevator-region*) gc
arrow-x arrow-y
arrow-width arrow-height
:fill-p))))))
;;;----------------------------------------------------------------------------+
;;; |
;;; Event Translations |
;;; |
;;;----------------------------------------------------------------------------+
(defevent scroller (:motion-notify :button-1) scroller-handle-motion)
(defevent scroller (:timer :update-delay) scroller-report-new-value)
(defevent scroller (:timer :click) (throw-action :click-timeout))
(defevent scroller (:button-release :button-1) scroller-handle-release)
(defevent scroller (:button-press :button-1) scroller-handle-press)
(defparameter *scroller-click-timeout* 0.2
"Number of seconds to wait before starting continuous scrolling")
(defparameter *scroller-hold-timeout* 0.05
"Number of seconds to wait during continuous scrolling before updating value")
(let ((press-handlers (make-array 7)))
(flet
((find-region (scroller)
;; Return index of scroller region containing the current event
(with-slots (regions orientation) scroller
(with-event (child x y)
(if child
;; Look up event child window among scroller regions
(let ((region (position child regions :test #'eq)))
(if (= region *elevator-region*)
;; Which part of elevator got the press: less-arrow, drag-area, or more-arrow?
(let
((region (+ *less-arrow-region*
(floor
(- (if (eq :vertical orientation) y x)
(scroller-value-position scroller))
(scrollbar-anchor-width
(getf *scrollbar-dimensions*
(contact-scale scroller)))))))
(if (and (= region *drag-area-region*)
(scrollbar-abbreviated-p scroller))
*more-arrow-region*
region))
;; Min/max anchor press
region))
;; Event occurred on non-child area of scroller
*cable-region*))))
(press-cable (scroller)
(with-slots (orientation increment width height indicator-size update-delay display value) scroller
(with-event (x y)
(multiple-value-bind (event-position max-position)
(if (eq :vertical orientation) (values y height) (values x width))
(let*
((anchor-height (scrollbar-anchor-height
(getf *scrollbar-dimensions* (contact-scale scroller))))
(min-position anchor-height)
(max-position (- max-position anchor-height))
(pane-size (let ((size (true-indicator-size indicator-size)))
(if (plusp size) size increment)))
(pane-increment (if (< event-position (scroller-value-position scroller))
;; Decrement by pane?
(when (>= event-position min-position)
(- pane-size))
;; Increment by pane?
(when (<= event-position max-position)
pane-size))))
(unless pane-increment
;; Just wait for release and do nothing
(catch :release (loop (process-next-event display)))
(return-from press-cable))
(if (catch :release
;; If user is clicking fast on cable, then we can arrive here
;; before all :exposure's from previous clicks have been processed.
;; Therefore, must use a timer, so we can continue processing :exposure's
;; while waiting for click release.
(add-timer scroller :click *scroller-click-timeout*)
(unwind-protect
(catch :click-timeout
(loop (process-next-event display)))
(delete-timer scroller :click))
t)
;; Perform continuous pane scrolling...
(let ((current-x x) (current-y y))
;; Set timer for update
(when (and (numberp update-delay) (plusp update-delay))
(add-timer scroller :update-delay update-delay))
;; Increment and warp pointer as needed, until release event
(apply-callback scroller :begin-continuous)
(catch :release
(loop
(scroller-increment-value scroller pane-increment)
(multiple-value-setq (current-x current-y)
(scrollbar-cable-warp
scroller pane-increment
current-x current-y
min-position max-position))
;; Wait for timeout to elapse
(do () ((not (process-next-event display *scroller-hold-timeout*))))))
(apply-callback scroller :end-continuous))
;; Single click -- increment value
(progn
(scroller-increment-value scroller pane-increment)
;; Warp pointer to keep it between elevator and anchor
(scrollbar-cable-warp
scroller pane-increment
x y
min-position max-position)))
;; Report final value, if necessary
(unless (eql 0 update-delay)
(delete-timer scroller :update-delay)
(apply-callback scroller :new-value value)))))))
(press-drag-area (scroller)
(with-slots (display regions value update-delay foreground orientation) scroller
(let
((highlight-pixel (logxor foreground (contact-current-background-pixel scroller)))
(elevator (svref regions *elevator-region*)))
(multiple-value-bind (drag-x drag-y drag-width drag-height)
(scrollbar-drag-area-geometry scroller)
(using-gcontext
(gc :drawable scroller
:function boole-xor
:foreground highlight-pixel)
;; Highlight drag area
(draw-rectangle
elevator gc
drag-x drag-y drag-width drag-height :fill-p)
(with-event (x y)
(let ((*previous-position* (if (eq :vertical orientation) y x))
(*drag-motion* t))
(declare (special *previous-position* *drag-motion*))
;; Set timer for update
(when (and (numberp update-delay) (plusp update-delay))
(add-timer scroller :update-delay update-delay))
;; Handle motion events until release.
(catch :release
(loop (process-next-event display)))))
;; Report final value.
(when (and (numberp update-delay) (plusp update-delay))
(delete-timer scroller :update-delay))
(apply-callback scroller :new-value value)
;; Unhighlight drag area
(draw-rectangle
elevator gc
drag-x drag-y drag-width drag-height :fill-p))))))
(press-less-arrow (scroller)
(with-slots (display regions value maximum increment update-delay foreground orientation) scroller
(let
((highlight-pixel (logxor foreground (contact-current-background-pixel scroller))))
(multiple-value-bind (arrow-x arrow-y arrow-width arrow-height)
(scrollbar-less-arrow-geometry scroller)
(using-gcontext
(gc :drawable scroller
:function boole-xor
:foreground highlight-pixel)
;; Highlight arrow
(draw-rectangle
(svref regions *elevator-region*) gc
arrow-x arrow-y arrow-width arrow-height :fill-p)
;; Force pointer to stay within arrow window
(grab-pointer scroller #.(make-event-mask :button-press :button-release)
:confine-to (svref regions *less-arrow-region*))
(if (catch :release (not (process-next-event display *scroller-click-timeout*)))
;; Perform continuous scrolling...
(progn
;; Set timer for update
(when (and (numberp update-delay) (plusp update-delay))
(add-timer scroller :update-delay update-delay))
;; Increment until release event
(apply-callback scroller :begin-continuous)
(catch :release
(loop
(scroller-increment-value scroller (- increment))
(do () ((not (process-next-event display *scroller-hold-timeout*))))))
(apply-callback scroller :end-continuous))
;; Single click -- increment value.
(scroller-increment-value scroller (- increment)))
;; Report final value, if necessary
(unless (eql 0 update-delay)
(delete-timer scroller :update-delay)
(apply-callback scroller :new-value value))
;; Release grab
(ungrab-pointer display)
;; Unhighlight arrow
(draw-rectangle
(svref regions *elevator-region*) gc
arrow-x arrow-y arrow-width arrow-height :fill-p))))))
(press-more-arrow (scroller)
(with-slots (display regions value maximum increment update-delay foreground orientation) scroller
(let
((highlight-pixel (logxor foreground (contact-current-background-pixel scroller))))
(multiple-value-bind (arrow-x arrow-y arrow-width arrow-height)
(scrollbar-more-arrow-geometry scroller)
(using-gcontext
(gc :drawable scroller
:function boole-xor
:foreground highlight-pixel)
;; Highlight arrow
(draw-rectangle
(svref regions *elevator-region*) gc
arrow-x arrow-y arrow-width arrow-height :fill-p)
;; Force pointer to stay within arrow window
(grab-pointer scroller #.(make-event-mask :button-press :button-release)
:confine-to (svref regions *more-arrow-region*))
(if (catch :release (not (process-next-event display *scroller-click-timeout*)))
;; Perform continuous scrolling...
(progn
;; Set timer for update
(when (and (numberp update-delay) (plusp update-delay))
(add-timer scroller :update-delay update-delay))
;; Increment until release event
(apply-callback scroller :begin-continuous)
(catch :release
(loop
(scroller-increment-value scroller increment)
(do () ((not (process-next-event display *scroller-hold-timeout*))))))
(apply-callback scroller :end-continuous))
;; Single click -- increment value.
(scroller-increment-value scroller increment))
;; Report final value, if necessary
(unless (eql 0 update-delay)
(delete-timer scroller :update-delay)
(apply-callback scroller :new-value value))
;; Release grab
(ungrab-pointer display)
;; Unhighlight arrow
(draw-rectangle
(svref regions *elevator-region*) gc
arrow-x arrow-y arrow-width arrow-height :fill-p))))))
(press-max-anchor (scroller)
(with-slots (display regions foreground maximum value) scroller
;; Highlight max anchor
(let
((max-anchor (svref regions *max-anchor-region*))
(highlight-size (scrollbar-anchor-width
(getf *scrollbar-dimensions* (contact-scale scroller)))))
;; This rectangle size is "too big", but we let the server clip it
(using-gcontext (gc :drawable scroller :foreground foreground)
(draw-rectangle
max-anchor gc
0 0 highlight-size highlight-size
:fill-p))
;; Wait for release event
(catch :release
(loop (process-next-event display)))
;; Unhighlight max anchor
(clear-area max-anchor)
;; Go to maximum position
(unless (= value maximum)
(setf (scale-value scroller) maximum)
(apply-callback scroller :new-value maximum)))))
(press-min-anchor (scroller)
(with-slots (display regions foreground minimum value) scroller
;; Highlight min anchor
(let
((min-anchor (svref regions *min-anchor-region*))
(highlight-size (scrollbar-anchor-width
(getf *scrollbar-dimensions* (contact-scale scroller)))))
;; This rectangle size is "too big", but we let the server clip it
(using-gcontext (gc :drawable scroller :foreground foreground)
(draw-rectangle
min-anchor gc
0 0 highlight-size highlight-size
:fill-p))
;; Wait for release event
(catch :release
(loop (process-next-event display)))
;; Unhighlight min anchor
(clear-area min-anchor)
;; Go to minimum position
(unless (= value minimum)
(setf (scale-value scroller) minimum)
(apply-callback scroller :new-value minimum))))))
;; Initialize press-handlers dispatch vector
(setf (svref press-handlers *elevator-region*) nil) ; should never be used!!
(setf (svref press-handlers *min-anchor-region*) #'press-min-anchor)
(setf (svref press-handlers *max-anchor-region*) #'press-max-anchor)
(setf (svref press-handlers *less-arrow-region*) #'press-less-arrow)
(setf (svref press-handlers *drag-area-region*) #'press-drag-area)
(setf (svref press-handlers *more-arrow-region*) #'press-more-arrow)
(setf (svref press-handlers *cable-region*) #'press-cable)
;; Define press action function
(defun scroller-handle-press (scroller)
(let ((*scroller-pressed-p* t))
(declare (special *scroller-pressed-p*))
(funcall (svref press-handlers (find-region scroller)) scroller)))))
(defun scroller-handle-release (scroller)
(declare (ignore scroller))
(declare (special *scroller-pressed-p*))
(when (boundp '*scroller-pressed-p*)
(throw :release nil)))
(defun scroller-handle-motion (scroller)
(declare (special *previous-position* *drag-motion*))
(when (boundp '*drag-motion*)
(with-slots (orientation) (the scroller scroller)
(with-event (state x y)
(multiple-value-bind (ptr-x ptr-y)
;; Is :button-1 still down?
(if (plusp (logand state #.(make-state-mask :button-1)))
;; Yes, query current pointer position
(query-pointer scroller)
;; No, use final x,y returned for button transition
(values x y))
(let* ((new-position (if (eq :vertical orientation) ptr-y ptr-x))
(increment (scroller-pixel-value scroller (- new-position *previous-position*))))
(unless (zerop increment)
(scroller-increment-value scroller increment)
(setf *previous-position* new-position))))))))
(defun scroller-report-new-value (scroller)
(with-slots (value) (the scroller scroller)
(apply-callback scroller :new-value value)))
(defun scroller-increment-value (scroller increment)
(with-slots (value minimum maximum update-delay) (the scroller scroller)
(let*
((new-value (+ value increment))
(adjusted (min (max (or (apply-callback scroller :adjust-value new-value)
new-value)
minimum)
maximum)))
(unless (= adjusted value)
(setf (scale-value scroller) adjusted)
(when (eql 0 update-delay)
(apply-callback scroller :new-value adjusted))))))
(defun scrollbar-cable-warp (scroller pane-increment current-x current-y min-position max-position)
(with-slots (orientation) (the scroller scroller)
(let*
((current-position
(if (eq :vertical orientation) current-y current-x))
(new-pointer-position
(if (plusp pane-increment)
(when (< current-position (setf min-position
(+ (scroller-value-position scroller)
(scrollbar-elevator-size scroller))))
(1+ min-position))
(when (> current-position (setf max-position
(scroller-value-position scroller)))
(1- max-position)))))
(when new-pointer-position
(if (eq :vertical orientation)
(setf current-y new-pointer-position)
(setf current-x new-pointer-position))
(warp-pointer scroller current-x current-y))
(values current-x current-y))))
(defun scroller-value-position (scroller)
(with-slots (width height orientation value minimum maximum) (the scroller scroller)
(let*
((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
(margin (scrollbar-margin dimensions))
(anchor-height (scrollbar-anchor-height dimensions))
(range (- maximum minimum)))
(+ anchor-height
margin
(if (zerop range) 0
(pixel-round
(* (- value minimum)
;; Pixels per value unit
(/ (- (if (eq orientation :vertical) height width)
anchor-height margin
(* (scrollbar-anchor-width dimensions)
(if (scrollbar-abbreviated-p scroller) 2 3))
2
margin anchor-height)
range))))))))
(defun scroller-pixel-value (scroller pixels)
(with-slots (width height orientation minimum maximum increment) (the scroller scroller)
(let*
((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
(margin (scrollbar-margin dimensions))
(anchor-height (scrollbar-anchor-height dimensions)))
;; pixels times value-units-per-pixel, rounded to nearest multiple of increment
(* (pixel-round
(/ (* pixels (- maximum minimum))
(- (if (eq orientation :vertical) height width)
anchor-height margin
(* (scrollbar-anchor-width dimensions)
(if (scrollbar-abbreviated-p scroller) 2 3))
2
margin anchor-height))
increment)
increment))))
(defun scroller-indicator-position (scroller &optional size)
(setf size (or size (scroller-indicator-size scroller)))
(with-slots (width height orientation value minimum maximum) (the scroller scroller)
(let*
((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
(margin (scrollbar-margin dimensions))
(anchor-height (scrollbar-anchor-height dimensions))
(range (- maximum minimum)))
(+ anchor-height
margin
(if (zerop range) 0
(pixel-round
(* (- value minimum)
;; Pixels per value unit for indicator position
(/ (- (if (eq orientation :vertical) height width)
anchor-height margin
size
margin anchor-height)
range))))))))
(defun scroller-indicator-size (scroller)
(with-slots (width height orientation minimum maximum indicator-size) (the scroller scroller)
(let*
((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
(margin (scrollbar-margin dimensions))
(anchor-height (scrollbar-anchor-height dimensions))
(range (- maximum minimum)))
(pixel-round
(*
(min (true-indicator-size indicator-size) range) ; "clip" displayed size to cable range
(if (zerop range) 0
;; Pixels per value unit for indicator size
(/ (- (if (eq orientation :vertical) height width)
anchor-height margin
margin anchor-height)
range)))))))